﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace OpGen.Compiler.Generator
{
    /// <summary>
    /// Stellt Basisfunktionen zum erstellen von Quellcode Dateien bereit
    /// </summary>
    public abstract class CodeWriter
    {
        private StreamWriter _streamWriter;

        /// <summary>
        /// Erstellt eine neue Instanz der CodeWriter Klasse
        /// </summary>
        protected CodeWriter() :this(new MemoryStream())
        { }

        /// <summary>
        /// Erstellt eine neue Instanz der CodeWriter Klasse
        /// </summary>
        /// <param name="stream"></param>
        protected CodeWriter(Stream stream)
        {
            _streamWriter = new StreamWriter(stream);
            BlockSpaceIndex = 0;
            BlockSpaceSize = 4;
        }

        /// <summary>
        /// Schreibt eine Zeile unter Berücksichtigung der AutoNewLine Option
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        protected virtual void Write(string format, params object[] args)
        {
            if (AutoNewLine)
                WriteLine(format, args);
            else
                _streamWriter.Write(format, args);

            _streamWriter.Flush();
        }

        /// <summary>
        /// Schreibt eine Zeile
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        protected virtual void WriteLine(string format,params object[] args)
        {
            if (UseBlockSpace && AutoNewLine)
                format = new string(' ', BlockSpaceIndex * BlockSpaceSize) + format;

            _streamWriter.WriteLine(format, args);

            _streamWriter.Flush();
        }

        /// <summary>
        /// Gibt an das immer ein Zeilenumbruch verwendet wird
        /// </summary>
        public bool AutoNewLine { get; set; }

        /// <summary>
        /// Gibt an das Tabulator einrücken erzeugt werden soll
        /// </summary>
        public bool UseBlockSpace { get; set; }

        /// <summary>
        /// Liefert die aktuelle tiefe des Block-Levels
        /// </summary>
        protected int BlockSpaceIndex { get; private set; }

        /// <summary>
        /// Liefert die Anzahl der Leerzeichen die durch die Blocktiefe definiert ist
        /// </summary>
        protected int BlockSpaceSize { get; set; }

        /// <summary>
        /// Startet ein neuen Blockeinzug
        /// </summary>
        /// <param name="text"></param>
        /// <param name="args"></param>
        protected void BeginBlock(string text, params object[] args)
        {
            Write(text, args);
            BlockSpaceIndex++;
        }

        /// <summary>
        /// Beendet einen Blockeinzug
        /// </summary>
        /// <param name="text"></param>
        /// <param name="args"></param>
        protected void EndBlock(string text, params object[] args)
        {
            BlockSpaceIndex--;
            Write(text, args);
        }

        /// <summary>
        /// Schreibt eine Zeile und begint einen Blockeinzug
        /// </summary>
        /// <param name="text"></param>
        protected void BeginBlockLine(string text)
        {
            WriteLine(text);
            BlockSpaceIndex++;
        }

        /// <summary>
        /// Schreibt eine Zeile und beendet einen Blockeinzug
        /// </summary>
        /// <param name="Text"></param>
        protected void EndBlockLine(string Text)
        {
            BlockSpaceIndex--;
            WriteLine(Text);
        }

        /// <summary>
        /// Liefert den Stream in dem die Ausgabedaten geschrieben werden
        /// </summary>
        public Stream BaseStream { get { return _streamWriter.BaseStream;} }

        /// <summary>
        /// Gibt die Standart Datentypen zurück
        /// </summary>
        /// <param name="Element"></param>
        /// <returns></returns>
        protected virtual string GetTypeName(Element Element)
        {
            var Ret = "";

            switch (Element.Type.Type)
            {
                case Types.INT16:
                    Ret = "Int16";
                    break;
                case Types.INT:
                case Types.INT32:
                    Ret = "int";
                    break;
                case Types.INT64:
                case Types.LONG:
                    Ret = "long";
                    break;
                case Types.FLOAT:
                    Ret = "float";
                    break;
                case Types.DOUBLE:
                    Ret = "double";
                    break;
                case Types.DECIMAL:
                    Ret = "decimal";
                    break;
                case Types.STRING:
                    Ret = "string";
                    break;
                case Types.BYTE:
                    Ret = "byte";
                    break;
                case Types.BOOL:
                    Ret = "bool";
                    break;
                case Types.CHAR:
                    Ret = "char";
                    break;
                case Types.DATETIME:
                    Ret = "DateTime";
                    break;
                case Types.REFERENCE:
                    Ret = Element.Type.ReferenceElement.Fullname;
                    break;
                case Types.NONE:
                    break;
                default:
                    break;
            }

            if (Element.Type.IsList)
                Ret = string.Format("List<{0}>", Ret);

            return Ret;
        }

        /// <summary>
        /// Liefert den Zugriffsbeschränkung
        /// </summary>
        /// <param name="Element"></param>
        /// <returns></returns>
        protected virtual string GetModifier(Element Element)
        {
            switch (Element.Modifier)
            {
                case Modifiers.None:
                    return "";
                case Modifiers.Unkown:
                    return "public";
                case Modifiers.Private:
                    return "private";
                case Modifiers.Public:
                    return "public";
                case Modifiers.Internal:
                    return "internal";
                case Modifiers.Protected:
                    return "protected";
                default:
                    return "";
            }
        }

        /// <summary>
        /// Generiert BinaryReader Daten
        /// </summary>
        /// <param name="BO"></param>
        /// <param name="BA"></param>
        /// <param name="Field"></param>
        /// <returns></returns>
        protected virtual string GetBinaryReaderMethodeName(string BO, string BA, FieldElement Field)
        {
            switch (Field.Type.Type)
            {
                case Types.INT16:
                    return string.Format("{0}.ReadInt16()",BO, BA);
                case Types.INT:
                case Types.INT32:
                    return string.Format("{0}.ReadInt32()", BO, BA);
                case Types.INT64:                    
                case Types.LONG:
                    return string.Format("{0}.ReadInt64()", BO, BA);
                case Types.FLOAT:
                    return string.Format("{0}.ReadSingle()", BO, BA);
                case Types.DOUBLE:
                    return string.Format("{0}.ReadDouble()", BO, BA);
                case Types.DECIMAL:
                    return string.Format("{0}.ReadDecimal()", BO, BA);
                case Types.STRING:
                    return string.Format("{0}.ReadString()", BO, BA);
                case Types.BYTE:
                    return string.Format("{0}.ReadByte()", BO, BA);
                case Types.BOOL:
                    return string.Format("{0}.ReadBoolean()", BO, BA);
                case Types.CHAR:
                    return string.Format("{0}.ReadChar()", BO, BA);
                case Types.DATETIME:
                    return string.Format("DateTime.FromBinary({0}.ReadInt64())", BO, BA);
                case Types.REFERENCE:
                    return string.Format("new {0}({1})", Field.ParentElement.Elementname, BA);
                case Types.NONE:
                default:
                    return string.Format("{0}.ReadString()", BO, BA);
            }
        }

        /// <summary>
        /// Generiert BinaryWriter Daten
        /// </summary>
        /// <param name="BO"></param>
        /// <param name="Field"></param>
        /// <param name="IndexerChar"></param>
        /// <returns></returns>
        protected virtual string GetBinaryWriterMethodeName(string BO, FieldElement Field, string IndexerChar)
        {
            var Indexer = "";

            if (Field.Type.IsList)
                Indexer = "[" + IndexerChar + "]";

            switch (Field.Type.Type)
            {
                case Types.INT16:
                case Types.INT:
                case Types.INT32:
                case Types.INT64:
                case Types.LONG:
                case Types.FLOAT:
                case Types.DOUBLE:
                case Types.DECIMAL:
                case Types.STRING:
                case Types.BYTE:
                case Types.BOOL:
                case Types.CHAR:
                    return string.Format("{0}.Write(_{1}{2});", BO, Field.Elementname,Indexer);
                case Types.DATETIME:
                    return string.Format("{0}.Write(_{1}{2}.ToBinary());", BO, Field.Elementname, Indexer);
                case Types.REFERENCE:
                    return string.Format("{0}.Write(_{1}{2}.ToArray());", BO, Field.Elementname, Indexer);
                case Types.NONE:
                default:
                    return string.Format("{0}.Write(_{1}{2});", BO, Field.Elementname, Indexer);
            }
        }

        /// <summary>
        /// Schreibt denn OpGen Header
        /// </summary>
        /// <param name="Element"></param>
        public abstract void WriteTitleInfo(ProjectElement element);

        /// <summary>
        /// Schreibt die Namespace-Referenzen
        /// </summary>
        /// <param name="Element"></param>
        public abstract void WriteImports(ProjectElement element);

        /// <summary>
        /// Schreibt den Beginn einer Namespace Struktur
        /// </summary>
        /// <param name="Element"></param>
        public abstract void BeginNamespace(NamespaceElement element);

        /// <summary>
        /// Schreibt den Beginn einer Namespace Struktur
        /// 
        /// Wird für das OpCode Enum verwendet
        /// </summary>
        /// <param name="Element"></param>
        public abstract void BeginNamespace(ProjectElement element);

        /// <summary>
        /// Schreibt das Ende einer Namespace Struktur
        /// </summary>
        public abstract void EndNamespace();

        /// <summary>
        /// Schreibt den Beginn einer Klassen Struktur
        /// </summary>
        /// <param name="Element"></param>
        public abstract void BeginClass(ClassElement element, bool useExtensions);

        /// <summary>
        /// Schreibt das Ende einer Klassen Struktur
        /// </summary>
        public abstract void EndClass();

        /// <summary>
        /// Schreibt den Beginn einer Enum Struktur
        /// </summary>
        /// <param name="Element"></param>
        public abstract void BeginEnum(EnumElement element);

        /// <summary>
        /// Schreibt den Beginn einer Enum Struktur.
        /// 
        /// Wird für das OpCode Enum verwendet
        /// </summary>
        /// <param name="Element"></param>
        public abstract void BeginEnum(ProjectElement element);

        /// <summary>
        /// Schreibt einen Enum-Item eintrag
        /// </summary>
        /// <param name="Element"></param>
        public abstract void EnumItem(EnumElement element);

        /// <summary>
        /// Schreibt das Ende einer Enum Struktur
        /// </summary>
        public abstract void EndEnum();

        /// <summary>
        /// Schreibt den Beginn einer Klassen Struktur
        /// 
        /// Mit OpCode-Message Erweiterungen.
        /// </summary>
        /// <param name="Element"></param>
        public abstract void BeginMessage(MessageElement element, bool useExtensions);

        /// <summary>
        /// Schreibt das Ende einer Klassen Struktur
        /// </summary>
        public abstract void EndMessage();

        /// <summary>
        /// Schreibt den Beginn eines Feldes
        /// </summary>
        /// <param name="Element"></param>
        public abstract void BeginField(FieldElement element);

        /// <summary>
        /// Schreibt das Ende eines Feldes
        /// </summary>
        /// <param name="Element"></param>
        public abstract void EndField(FieldElement element);

        /// <summary>
        /// Schreibt den Beginn einer Eigenschaft
        /// </summary>
        /// <param name="Element"></param>
        public abstract void BeginProperty(FieldElement element);

        /// <summary>
        /// Schreibt den Getter einer Eigenschaft
        /// </summary>
        /// <param name="Element"></param>
        public abstract void GetterProperty(FieldElement element);

        /// <summary>
        /// Schreibt den Setter einer Eigenschaft
        /// </summary>
        /// <param name="Element"></param>
        public abstract void SetterProperty(FieldElement element);

        /// <summary>
        /// Schreibt das Ende einer Eigenschaft
        /// </summary>
        /// <param name="Element"></param>
        public abstract void EndProperty(FieldElement element);

        /// <summary>
        /// Schreibt den Konstruktor
        /// </summary>
        /// <param name="Element"></param>
        public abstract void Constructor(Element element);

        /// <summary>
        /// Schreibt zusätzliche Methoden
        /// </summary>
        /// <param name="Element"></param>
        public abstract void Methods(Element element, bool useExtensions);
    }
}
